﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace net45
{
    public class Server
    {
        private readonly HttpListener listener;
        private readonly ASCIIEncoding encoding;
        private const string dataDirectory = @"..\..\..\..\data";
        private const int maxfiles = 550;

        public Server(string address)
        {
            this.encoding = new ASCIIEncoding();
            this.listener = new HttpListener();
            this.listener.Prefixes.Add(address);
            this.listener.Start();
        }

        public async Task Start()
        {
            while (true)
            {
                var context = await this.listener.GetContextAsync();
                await this.ProcessRequest(context);
            }
        }
        private string GetFileFromUrl(string url)
        {
            var file = 1;

            if (!String.IsNullOrEmpty(url)) file = (Int32.Parse(url) % maxfiles);

            return String.Format("input{0}.txt", file.ToString().PadLeft(3, '0'));
        }
        private async Task ProcessRequest(HttpListenerContext context)
        {
            try
            {
                var filename = this.GetFileFromUrl(context.Request.Url.PathAndQuery.Substring(1));
                string rawData = string.Empty;

                using (StreamReader reader = new StreamReader(Path.Combine(dataDirectory, filename)))
                {
                    rawData = await reader.ReadToEndAsync();
                }

                var sorted = await this.SortAsync(context, rawData);// this.Sort( rawData);
                var response = encoding.GetBytes(String.Format("{0}\t{1}", filename, sorted[sorted.Length / 2]));

                await context.Response.OutputStream.WriteAsync(response, 0, response.Length);
                context.Response.StatusCode = (int)HttpStatusCode.OK;
            }
            catch (Exception e)
            {
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                Console.WriteLine(e.Message);
            }
            finally
            {
                context.Response.Close();
            }
        }

       
        private async Task<string[]> SortAsync(HttpListenerContext context, string rawData)
        {
            return await Task.Factory.StartNew(() =>
            {
                var array = rawData.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                Array.Sort(array);
                //QuicksortParallelOptimised<string>(array);
                return array;
            });
        }
        private string[] Sort(string rawData)
        {
            var array = rawData.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            QuicksortParallelOptimised<string>(array);
            // array.AsParallel<string>().OrderBy<string, string>((i) => { return i; });
            //Array.Sort(array);            
            return array;
        }
        private void Swap<T>(T[] arr, int i, int j)
        {
            T tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }

        private int Partition<T>(T[] arr, int low, int high) where T : IComparable<T>
        {
            // Simply partitioning implementation

            int pivotPos = (high + low) / 2;
            T pivot = arr[pivotPos];
            Swap(arr, low, pivotPos);

            int left = low;
            for (int i = low + 1; i <= high; i++)
            {
                if (arr[i].CompareTo(pivot) < 0)
                {
                    left++;
                    Swap(arr, i, left);
                }
            }

            Swap(arr, low, left);
            return left;
        }
        private void QuicksortSequential<T>(T[] arr, int left, int right)
            where T : IComparable<T>
        {
            if (right > left)
            {
                int pivot = Partition(arr, left, right);
                QuicksortSequential(arr, left, pivot - 1);
                QuicksortSequential(arr, pivot + 1, right);
            }
        }
        private void QuicksortParallelOptimised<T>(T[] arr) where T : IComparable<T>
        {
            QuicksortParallelOptimised(arr, 0, arr.Length - 1);
        }


        private void QuicksortParallelOptimised<T>(T[] arr, int left, int right)
            where T : IComparable<T>
        {
            const int SEQUENTIAL_THRESHOLD = 2048;
            if (right > left)
            {
                if (right - left < SEQUENTIAL_THRESHOLD)
                {

                    QuicksortSequential(arr, left, right);
                }
                else
                {
                    int pivot = Partition(arr, left, right);
                    Parallel.Invoke(
                        () => QuicksortParallelOptimised(arr, left, pivot - 1),
                        () => QuicksortParallelOptimised(arr, pivot + 1, right));
                }
            }
        }

        static void Main(string[] args)
        {
            const string address = "http://localhost:8080/";
            var program = new Server(address);
            program.Start().Wait();
        }
    }
}
